package top.lingkang.mm;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.lingkang.mm.error.MagicException;
import top.lingkang.mm.gen.DefaultIdGenerate;
import top.lingkang.mm.gen.IdGenerate;
import top.lingkang.mm.handler.MagicSqliteDateTypeHandler;
import top.lingkang.mm.override.MagicReflectorFactory;
import top.lingkang.mm.page.MagicPageInterceptor;
import top.lingkang.mm.transaction.MagicTransactionFactory;
import top.lingkang.mm.utils.MagicUtils;

import javax.sql.DataSource;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.*;

/**
 * mybatis-magic 主体配置，spring体系中不使用此配置
 *
 * @author lingkang
 * Created by 2024/3/3
 */
public class MagicConfiguration extends Configuration {
    private static final Logger log = LoggerFactory.getLogger(MagicConfiguration.class);
    private final List<String> scanMapperXml = new ArrayList<>();
    private DataSource dataSource;
    /**
     * 默认id生成算法，至少要保持 id 列长度为 12 位字符以上<br/>
     * 默认id算法：{@link top.lingkang.mm.gen.DefaultIdGenerate}
     */
    private IdGenerate idGenerate = new DefaultIdGenerate();
    private final List<String> amxCache = new ArrayList<>();
    private final Set<String> pmxCache = new HashSet<>();
    private boolean initDbType = false;

    public MagicConfiguration() {
        this(null);
    }

    public MagicConfiguration(DataSource dataSource) {
        this.dataSource = dataSource;
        // 反射工厂，将下划线转驼峰
        setReflectorFactory(new MagicReflectorFactory());
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public MagicConfiguration setDataSource(DataSource dataSource) {
        if (dataSource == null)
            throw new MagicException("dataSource 不能为空！");
        this.dataSource = dataSource;
        return this;
    }

    /**
     * 添加 mapper xml映射文件。可以是绝对路径的xml、也能是相对路径的xml(ClassPath)<br/>
     * 也可以是相对路径的目录: mapper（位于：src/main/resources/mapper）<br/>
     * 也可以是绝对目录: C:\Users\Administrator\Desktop\mapperXmlFiles <br/>
     * 建议指明xml：<br/>
     * <pre>
     * {@code
     * // 文件加载 resources/mapper/UserMapper.xml
     * configuration.addMapperXml("mapper/UserMapper.xml");
     * }
     * </pre>
     *
     * @param mapperXml 相对资源路径下
     * @return
     */
    public synchronized MagicConfiguration addMapperXml(String... mapperXml) {
        if (mapperXml == null)
            throw new MagicException("addMapperXml 不能添加空值");

        for (final String xml : mapperXml) {
            if (amxCache.contains(xml))// 重复扫描时跳过
                continue;
            if (xml.endsWith(".xml") && !xml.endsWith("*.xml")) {
                if (pmxCache.contains(xml)) {
                    log.debug("无法重复加载xml，已加载：{}", xml);
                    continue;
                }
                InputStream stream = ResourceUtil.getStream(xml);
                log.debug("加载 mapper xml: {}", xml);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(stream, this, xml,
                        getSqlFragments());
                try {
                    mapperParser.parse();
                    pmxCache.add(xml);
                } catch (Exception e) {
                    if (e.getMessage().contains("already contains key"))
                        log.error("存在重复的接口方法，它可能与 BaseMapper 中的接口名称冲突");
                    throw e;
                }
                IoUtil.close(stream);
            } else {
                String targetXml = null;
                URL url = getClass().getClassLoader().getResource("");
                if (url == null) {
                    if (xml.endsWith("*")) {
                        targetXml = xml + ".xml";
                    } else if (xml.endsWith("/")) {
                        targetXml = xml + "*.xml";
                    } else {
                        targetXml = xml + "/*.xml";
                    }
                    List<String> list = MagicUtils.scanResource(targetXml);
                    for (String file : list) {
                        loadMapperXmlByFile(file, true);
                    }
                } else {
                    if (xml.endsWith("**")) {
                        targetXml = xml.substring(0, xml.length() - 2);
                    } else if (xml.endsWith("**.xml")) {
                        targetXml = xml.substring(0, xml.length() - 6);
                    } else if (xml.endsWith("*.xml")) {
                        targetXml = xml.substring(0, xml.length() - 5);
                    } else
                        targetXml = xml;
                    List<File> list = FileUtil.loopFiles(targetXml);
                    for (File f : list) {
                        loadMapperXmlByFile(f.getPath());
                    }
                }
            }
            if (!xml.contains("*")) {
                List<URL> list = ResourceUtil.getResources(xml);
                for (URL url : list) {
                    String file = url.getFile();
                    loadMapperXmlByFile(file, true);
                }
            }
            amxCache.add(xml);
        }

        scanMapperXml.addAll(Arrays.asList(mapperXml));
        return this;
    }

    private void loadMapperXmlByFile(String filePath) {
        loadMapperXmlByFile(filePath, false);
    }

    private void loadMapperXmlByFile(String filePath, boolean isResource) {
        if (!filePath.endsWith(".xml")) {
            return;
        }
        if (!isResource) {
            if (!FileUtil.isFile(filePath)) {
                return;
            }
        }
        if (pmxCache.contains(filePath)) {
            log.debug("无法重复加载xml，已加载：{}", filePath);
            return;
        }
        InputStream inputStream = isResource ? MagicConfiguration.class.getClassLoader().getResourceAsStream(filePath) : FileUtil.getInputStream(filePath);
        log.debug("加载 mapper xml: {}", filePath);
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, this, filePath,
                getSqlFragments());
        try {
            mapperParser.parse();
            pmxCache.add(filePath);
        } catch (Exception e) {
            if (e.getMessage() != null && e.getMessage().contains("already contains key"))
                log.error("存在重复的接口方法，它可能与 BaseMapper 冲突");
            throw e;
        }

    }

    public List<String> getScanMapperXml() {
        return scanMapperXml;
    }

    public IdGenerate getIdGenerate() {
        return idGenerate;
    }

    public MagicConfiguration setIdGenerate(IdGenerate idGenerate) {
        this.idGenerate = idGenerate;
        return this;
    }

    public SqlSessionFactory build() {
        Environment environment = new Environment("magic", new MagicTransactionFactory(), dataSource);
        setEnvironment(environment);
        // 分页拦截
        addInterceptor(new MagicPageInterceptor());

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(this);

        // 代理会话工厂
        SqlSessionFactory sessionFactory = (SqlSessionFactory) Proxy.newProxyInstance(
                MagicConfiguration.class.getClassLoader(),
                new Class[]{SqlSessionFactory.class},
                new SqlSessionFactoryProxy(sqlSessionFactory)
        );

        if (!initDbType) {
            SqlSession sqlSession = sessionFactory.openSession();
            try {
                String url = MagicUtils.getDatabaseURL(sqlSession.getConnection(), true);
                initDbType = true;
                if (url != null) {
                    url = url.toLowerCase();
                    if (url.contains(":sqlite:")) {
                        // 针对sqlite中的时间进行转换
                        sqlSessionFactory.getConfiguration().getTypeHandlerRegistry()
                                .register(Date.class, new MagicSqliteDateTypeHandler());
                        log.info("注册sqlite特殊Date类型处理");
                    }
                }
            } catch (Exception e) {
                log.error("判断数据库连接类型错误", e);
            }
        }

        return sessionFactory;
    }
}
